﻿#region License, Terms and Conditions
//
// Jayrock - A JSON-RPC implementation for the Microsoft .NET Framework
// Written by Atif Aziz (www.raboof.com)
// Copyright (c) Atif Aziz. All rights reserved.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation; either version 3 of the License, or (at your option)
// any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
//
#endregion

#if !NET_1_0 && !NET_1_1

namespace Jayrock.GeoJson
{
    #region Improts

    using System;
    using System.Text;
    using Jayrock.Json;

    #endregion

    public delegate T GeoJsonPositionFunc<T>(double x, double y, double z);

    /// <summary>
    /// Represents a <a href="http://geojson.org/geojson-spec.html#positions">GeoJSON position</a>.
    /// </summary>

    [Serializable]
    public struct GeoJsonPosition : IEquatable<GeoJsonPosition>
    {
        public readonly double X;
        public readonly double Y;
        public readonly double Z;

        /// <summary>
        /// Initializes a new <see cref="GeoJsonPosition"/> with values
        /// for x and y components.
        /// </summary>
        /// <exception cref="ArgumentException">
        /// Either x or y is not a number (<see cref="double.NaN"/>).
        /// </exception>

        public GeoJsonPosition(double x, double y) :
            this(x, y, double.NaN) { }

        /// <summary>
        /// Initializes a new <see cref="GeoJsonPosition"/> with values
        /// for x, y and z components where z can be 
        /// <see cref="double.NaN"/> if undefined.
        /// </summary>
        /// <exception cref="ArgumentException">
        /// Either x or y is not a number (<see cref="double.NaN"/>).
        /// </exception>

        public GeoJsonPosition(double x, double y, double z)
        {
            if (double.IsNaN(x)) throw new ArgumentException(null, "x");
            if (double.IsNaN(y)) throw new ArgumentException(null, "y");

            X = x;
            Y = y;
            Z = z;
        }

        /// <summary>
        /// Determines if <see cref="Z"/> is defined or not.
        /// </summary>

        public bool HasZ { get { return double.IsNaN(Z); } }

        /// <summary>
        /// Folds/aggregates the position into another user-defined type by 
        /// supplying <see cref="X"/>, <see cref="Y"/> and <see cref="Z"/> 
        /// components to result function.
        /// </summary>

        public TResult Fold<TResult>(GeoJsonPositionFunc<TResult> resultFunc)
        {
            return resultFunc(X, Y, Z);
        }

        /// <summary>
        /// Indicates whether the current object is equal to another object 
        /// of the same type.
        /// </summary>

        public bool Equals(GeoJsonPosition other)
        {
            return other.X.Equals(X)
                   && other.Y.Equals(Y)
                   && other.Z.Equals(Z);
        }

        /// <summary>
        /// Indicates whether this instance and a specified object are equal.
        /// </summary>

        public override bool Equals(object obj)
        {
            return obj is GeoJsonPosition
                   && Equals((GeoJsonPosition)obj);
        }

        /// <summary>
        /// Returns the hash code for this instance.
        /// </summary>

        public override int GetHashCode()
        {
            unchecked
            {
                int result = X.GetHashCode();
                result = (result * 397) ^ Y.GetHashCode();
                result = (result * 397) ^ Z.GetHashCode();
                return result;
            }
        }

        /// <summary>
        /// Determines if two <see cref="GeoJsonPosition"/> objects are
        /// equivalent.
        /// </summary>

        public static bool operator ==(GeoJsonPosition left, GeoJsonPosition right)
        {
            return left.Equals(right);
        }

        /// <summary>
        /// Determines if two <see cref="GeoJsonPosition"/> objects are not
        /// equivalent.
        /// </summary>

        public static bool operator !=(GeoJsonPosition left, GeoJsonPosition right)
        {
            return !left.Equals(right);
        }

        /// <summary>
        /// Writes this GeoJSON position expressed as an array containing 
        /// two (x and y only) or three (x, y and z) JSON number elements.
        /// </summary>
        /// <remarks>
        /// If <see cref="Z"/> is <see cref="double.NaN"/> then it is not
        /// written in the resulting JSON array.
        /// </remarks>

        public void ToJson(JsonWriter writer)
        {
            ToJson(writer, false);
        }

        /// <summary>
        /// Writes this GeoJSON position expressed as an array containing 
        /// two (x and y only) or three (x, y and z) JSON number elements.
        /// An additional parameter specifies whether the supplied
        /// <see cref="JsonWriter"/> is flushed or not at the end.
        /// </summary>
        /// <remarks>
        /// If <see cref="Z"/> is <see cref="double.NaN"/> then it is not
        /// written in the resulting JSON array.
        /// </remarks>

        private void ToJson(JsonWriter writer, bool flush)
        {
            if (writer == null) throw new ArgumentNullException("writer");

            writer.WriteStartArray();
            writer.WriteNumber(X);
            writer.WriteNumber(Y);
            if (!double.IsNaN(Z))
                writer.WriteNumber(Z);
            writer.WriteEndArray();
            if (flush) writer.Flush();
        }

        /*
        public static GeoJsonPosition? Parse(JsonReader reader)
        {
            return Parse(reader, false);
        }

        // ReSharper disable InconsistentNaming
        public static GeoJsonPosition? ParseXY(JsonReader reader)
        // ReSharper restore InconsistentNaming
        {
            return Parse(reader, true);
        }

        private static GeoJsonPosition? Parse(JsonReader reader, bool noZed)
        {
            if (reader == null) throw new ArgumentNullException("reader");

            if (reader.MoveToContent())
                throw new FormatException("");

            reader.ReadToken(JsonTokenClass.Array);
            
            var x = reader.ReadNumber().ToDouble();
            var y = reader.ReadNumber().ToDouble();
            
            var z = double.NaN;
            if (!noZed)
            {
                if (reader.TokenClass == JsonTokenClass.Null)
                    reader.Read();
                else
                    z = reader.ReadNumber().ToDouble();
            }
            
            while (reader.TokenClass != JsonTokenClass.EndArray)
                reader.Skip();

            reader.ReadToken(JsonTokenClass.EndArray);

            return new GeoJsonPosition(x, y, z);
        }
        */

        /// <summary>
        /// Returns string containing a JSON text representation of this 
        /// GeoJSON position expressed as an array containing two (x and y
        /// only) or  three (x, y and z) JSON number elements.
        /// </summary>

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            ToJson(JsonText.CreateWriter(sb), true);
            return sb.ToString();
        }
    }
}

#endif // !NET_1_0 && !NET_1_1
